#include "DatabaseStuff.h"
#include "Debug.h"
#include "DatabaseInfo.h"
#include <string.h>
#include <stdio.h>

// Returns true if the server is a http or https url.
bool DatabaseStuff::IsServerAUrl(const char *server)
{
	if (strncmp("http://",server,7)==0) {
		return true;
	}
	if (strncmp("https://",server,8)==0) {
		return true;
	}
	return false;
}

void DatabaseStuff::GetLastBannedTime(const char *server,char *output,unsigned long maxLength)
{
	DEBUG;
	bool isUrl;
	mysqli db;
	unsigned long length;
	mysqli_stmt stmt;
	mysqli_bind outputs(1);
	isUrl = IsServerAUrl(server);
	if (isUrl) {
		// server is a url.
		throw "Url is not supported for this command.";
	} else {
		// server is a database.
		DEBUG;
    		OpenDatabase(db,server);
		DEBUG;
		PrepareGetLastBannedTimeQuery(db,stmt);
		DEBUG;
		int param_count = stmt.param_count();
		DEBUG;
		if (param_count != 0) {
			throw "Incorrect number of parameters.";
		}
		DEBUG;
		stmt.execute();
		DEBUG;
		// Build outputs for the loop.
		length = 0;
		outputs.bind(0,output,maxLength,length);
		DEBUG;
		if (!stmt.bind_result(outputs)) {
			throw "bind results failed.";
		}
		DEBUG;
		if (!stmt.store_result()) {
			throw "Error in store_result.";
		}	   
		strncpy(output,"No data returned.",maxLength);
		DEBUG;
		while (stmt.fetch())
		{
			DEBUG;
		}
		DEBUG;
		if ((length >= 0) && (length < maxLength)) {
			output[length] = 0;
		}
		DEBUG;
		stmt.close();
		DEBUG;
		db.close();
		DEBUG;
	}
}

 // Get the list of IPs to ban from the database.
void DatabaseStuff::GetIPsToBan(Tree<IPAddress> &tree,const char *server)
{
	const int BufferSize = 1024;
	const int OutputBannedIPSize = 50;
	char command[BufferSize+100];
	int ch;
	IPAddress *ip;
	mysqli db;
	bool outOfMemory;
	mysqli_stmt stmt;
	int inputBannedID;
	int outputBannedID;
	char outputBannedIP[OutputBannedIPSize+20];
	unsigned long outputBannedIPLength;
	int numberofsaves;
	bool isUrl;
	FILE *response;
	mysqli_bind outputs(2);

	DEBUG;
	outOfMemory = false;
	outputBannedIP[50] = 0;
	isUrl = IsServerAUrl(server);
	if (isUrl)
	{
		// server is a url.
		if (strlen(server) > BufferSize) {
			throw "Server url is longer than 1024 characters.";
		} 
		// This is unsafe if server comes from an untrusted source.
		snprintf(command,BufferSize+5,"curl --no-progress-meter \"%s\"",server);
		DEBUG;
		response = popen(command,"r");
		if (response == NULL) {
			throw "Error opening the server url.";
		}
	} else {
		// server is a database.
    		OpenDatabase(db,server);
		PrepareGetNewBannedQuery(db,stmt);
		int param_count = stmt.param_count();
		if (param_count != 1) {
			throw "Incorrect number of parameters.";
		}
		DEBUG;
		mysqli_bind inputs(1);
		inputBannedID = 0; // In the future, this will be a different number.
		inputs.bind(0,inputBannedID);
		stmt.bind_param(inputs);
		DEBUG;
		stmt.execute();
		DEBUG;
		// Build outputs for the loop.
		outputs.bind(0,outputBannedID);
		outputs.bind(1,outputBannedIP,OutputBannedIPSize,outputBannedIPLength);
		DEBUG;
		if (!stmt.bind_result(outputs)) {
			throw "bind results failed.";
		}
		DEBUG;
		if (!stmt.store_result()) {
			throw "Error in store_result.";
		}	   
	}
	DEBUG;
	numberofsaves = 0;
	if (isUrl) {
		// Find the first [
		while (true) {
			ch = fgetc(response);
			if (ch == EOF) {
				break;
			}
			if (ch == '[') {
				break;
			}
		}
	}
	// Loop throught the input.
	while (true)
       	{
		// Get one record.
		if (isUrl) {
			outputBannedID = 0;
			outputBannedIPLength = 0;
			if (ch == ']') {
				while (fgetc(response) != EOF) { };
				break;
			}
			if (ch == EOF) {
				break;
			}
			// Read outputBannedID
			while (true) {
				ch = fgetc(response);
				if ((ch == EOF) || (ch == ',')) {
					break;
				}
				outputBannedID = outputBannedID * 10 + ch - '0';
			}
			if (ch == EOF) {
				break;
			}
			// Read outputBannedIP.
			while (true) {
				ch = fgetc(response);
				if ((ch == EOF)
			       	|| (ch == ',')
			       	|| (ch == ']')) {
					break;
				}
				if (outputBannedIPLength == OutputBannedIPSize) {
					while (fgetc(response) != EOF) { };
					pclose(response);
					throw "The IP address is too long.";
				}
				if ((ch != '"') && (ch != 39)) {
					outputBannedIP[outputBannedIPLength++] = (char) ch;
				}
			}
			outputBannedIP[outputBannedIPLength] = 0;
		} else {
			// Read one record from the database.
			if (!stmt.fetch())
			{
				break;
			}
		}
		// Save the record.
		DEBUG;
		if (outOfMemory) {
			continue;
		}
		if (!IPAddress::IsIP(outputBannedIP)) {
			continue; // Is this an IP address? You can't trust the database because it might have been hacked.
		}
		ip = new IPAddress();
		if (ip == nullptr) {
			outOfMemory = true;
			continue;
		}
		*ip = outputBannedIP;
		if (ip->IP == nullptr) {
			delete ip;
			ip = nullptr;
			outOfMemory = true;
			continue;
		}
		if (!tree.Add(&ip,IPAddress::Compare)) {
			outOfMemory = true;
			if (ip != nullptr) {
				delete ip;
				ip = nullptr;
			}
			continue;
		}
		DEBUG;
	}
	if (isUrl) {
		pclose(response);
	} else {
		stmt.close();
		db.close();
	}
	DEBUG;
	if (outOfMemory) {
		DEBUG;
		throw "Out of memory reading the list of IPs from the database.";
	}
	DEBUG;
}

// Opens a database connection.
void DatabaseStuff::OpenDatabase(mysqli &db,const char *server)
{	
	char username[50];
	char password[50];
	char database[50];
	
	DEBUG;
	rot13(DatabaseInfo::UserName(),username);
	rot13(DatabaseInfo::Password(),password);
	rot13(DatabaseInfo::Database(),database);	
	
	DEBUG;
	db.real_connect(server,username,password,database,3306);
	DEBUG;
	return;
}

void DatabaseStuff::rot13(const char *original,char *output)
{
	// A simple cipher that's
	// good enough to beat someone with a hex editor.
	// Not exactly rot13.
	// Assumes that output is a buffer big enough to hold the data.
	// output is probably a char array on the stack.
	const char *input;
	char *loop;
	char ch;
	
	input = original;
	loop = output;
	while (true) {
		ch = *(input++);
		if (ch == 0) {
			break;
		}
		if ((ch >='A') && (ch <= 'Z')) {
			ch = ('A'+'Z') - ch;
		}
		if ((ch >='a') && (ch <= 'z')) {
			ch = ('a'+'z') - ch;
		}
		if ((ch >='0') && (ch <= '9')) {
			ch = ('0'+'9')-ch;
		}
		*(loop++) = ch;
	}
	*loop = 0;

	return;
}

// Prepares the query to get new banned IPs.
void DatabaseStuff::PrepareGetNewBannedQuery(mysqli &db,mysqli_stmt &stmt)
{
   char query[100];
   rot13("xzoo yzmmvw.hk_tvg_mvd_yzmmvw(?)",query); // "call banned.sp_get_new_banned(?)";
   db.prepare(query,strlen(query),stmt);
   return;
}
void DatabaseStuff::PrepareGetLastBannedTimeQuery(mysqli &db,mysqli_stmt &stmt)
{
	char query[100];
	DEBUG;
	rot13("xzoo yzmmvw.hk_tvg_ozhg_yzmmvw_grnv",query); // call banned.sp_get_last_banned_time
	DEBUG;
	db.prepare(query,strlen(query),stmt);
	DEBUG;
	return;
}
